/**
 * Create in 2012-10-22 下午9:25:20.
 */
package kit4j.app.notepad.ui.mediator;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

import kit4j.app.notepad.ui.panel.NotepadBorderPanel;
import kit4j.app.notepad.ui.window.Notepad;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author MiniKnife
 * 
 */
public class NotepadMenuMediator {

	private static final Logger log = LoggerFactory.getLogger(NotepadMenuMediator.class);

	public static final int MENU_STATE_SHOWING = 1;

	public static final int MENU_STATE_HIDING = -1;

	public static final int MENU_SATE_WAITING = 0;

	private Notepad notepad;

	private NotepadBorderPanel borderPanel;

	private MouseAdapter menuCtrlListener;

	private MouseAdapter moveNotepadListener;

	private ContainerListener containerListener;

	private int minMenuBorderThickness;

	private int maxMenuBorderThickness;

	private int rise;

	private int menuState;

	private final Object menuStateLock;

	private int lastValidMenuState;

	private int vector;

	private long timeInterval;

	private boolean inAction;

	private final Object inActionLock;

	private ExecutorService executor;

	/**
	 * @param notepad
	 * @param borderPanel
	 */
	public NotepadMenuMediator() {
		minMenuBorderThickness = 1;
		maxMenuBorderThickness = 35;
		rise = 3;
		timeInterval = 20;
		inAction = false;
		executor = Executors.newFixedThreadPool(1);
		menuStateLock = new Object();
		inActionLock = new Object();
	}

	public void initMediator(Notepad notepad, NotepadBorderPanel borderPanel, int menuState) {
		lastValidMenuState = menuState;

		setNotepad(notepad);
		setBorderPanel(borderPanel);
		setMenuState(menuState);
		initBorderPanel();
	}

	private void initBorderPanel() {
		menuCtrlListener = new MouseAdapter() {

			/**
			 * @see java.awt.event.MouseAdapter#mouseEntered(java.awt.event.MouseEvent)
			 */
			@Override
			public void mouseEntered(MouseEvent evt) {
				if (getMenuState() != MENU_STATE_SHOWING) {
					menuAct(MENU_STATE_SHOWING);
				}
			}

			/**
			 * @see java.awt.event.MouseAdapter#mouseExited(java.awt.event.MouseEvent)
			 */
			@Override
			public void mouseExited(MouseEvent evt) {
				NotepadBorderPanel panel = getBorderPanel();
				Point location = evt.getLocationOnScreen();
				SwingUtilities.convertPointFromScreen(location, panel);

				if (!getBorderPanel().contains(location)) {
					if (getMenuState() != MENU_STATE_HIDING) {
						menuAct(MENU_STATE_HIDING);
					}
				}
			}
		};
		moveNotepadListener = new MouseAdapter() {

			private int pressX;
			private int pressY;
			private int pressPadX;
			private int pressPadY;

			@Override
			public void mousePressed(MouseEvent e) {
				Object source = e.getSource();
				if (source == getBorderPanel().getTopPanel() || source == getBorderPanel().getLeftPanel()
						|| source == getBorderPanel().getBottomPanel() || source == getBorderPanel().getRightPanel()) {
					getNotepad().setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
				}

				pressX = e.getXOnScreen();
				pressY = e.getYOnScreen();
				Notepad notepad = getNotepad();
				pressPadX = notepad.getX();
				pressPadY = notepad.getY();
			}

			@Override
			public void mouseReleased(MouseEvent evt) {
				Object source = evt.getSource();
				if (source == getBorderPanel().getTopPanel() || source == getBorderPanel().getLeftPanel()
						|| source == getBorderPanel().getBottomPanel() || source == getBorderPanel().getRightPanel()) {
					getNotepad().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
				}
			}

			@Override
			public void mouseDragged(MouseEvent evt) {
				Notepad notepad = getNotepad();
				int dX = evt.getXOnScreen() - pressX;
				int dY = evt.getYOnScreen() - pressY;

				notepad.setLocation(pressPadX + dX, pressPadY + dY);
			}
		};
		containerListener = new ContainerListener() {

			@Override
			public void componentRemoved(ContainerEvent e) {
				Component comp = e.getChild();
				comp.removeMouseListener(menuCtrlListener);
				comp.removeMouseMotionListener(menuCtrlListener);
			}

			@Override
			public void componentAdded(ContainerEvent e) {
				Component comp = e.getChild();
				comp.addMouseListener(menuCtrlListener);
				comp.addMouseMotionListener(menuCtrlListener);

				if (comp instanceof JScrollPane) {
					Component view = ((JScrollPane) comp).getViewport().getView();
					view.addMouseListener(menuCtrlListener);
					view.addMouseMotionListener(menuCtrlListener);
				}
			}
		};

		getBorderPanel().addMouseListener(menuCtrlListener);
		getBorderPanel().setMenuBorderThickness(minMenuBorderThickness, minMenuBorderThickness, minMenuBorderThickness,
				minMenuBorderThickness);

		JPanel contentPanel = getBorderPanel().getContentPanel();
		contentPanel.addMouseListener(menuCtrlListener);
		contentPanel.addMouseListener(moveNotepadListener);
		contentPanel.addMouseMotionListener(menuCtrlListener);
		contentPanel.addMouseMotionListener(moveNotepadListener);
		contentPanel.addContainerListener(containerListener);

		JPanel topPanel = getBorderPanel().getTopPanel();
		topPanel.addMouseListener(menuCtrlListener);
		topPanel.addMouseListener(moveNotepadListener);
		topPanel.addMouseMotionListener(menuCtrlListener);
		topPanel.addMouseMotionListener(moveNotepadListener);
		topPanel.addContainerListener(containerListener);

		JPanel leftPanel = getBorderPanel().getLeftPanel();
		leftPanel.addMouseListener(menuCtrlListener);
		leftPanel.addMouseListener(moveNotepadListener);
		leftPanel.addMouseMotionListener(menuCtrlListener);
		leftPanel.addMouseMotionListener(moveNotepadListener);
		leftPanel.addContainerListener(containerListener);

		JPanel bottomPanel = getBorderPanel().getBottomPanel();
		bottomPanel.addMouseListener(menuCtrlListener);
		bottomPanel.addMouseListener(moveNotepadListener);
		bottomPanel.addMouseMotionListener(menuCtrlListener);
		bottomPanel.addMouseMotionListener(moveNotepadListener);
		bottomPanel.addContainerListener(containerListener);

		JPanel rightPanel = getBorderPanel().getRightPanel();
		rightPanel.addMouseListener(menuCtrlListener);
		rightPanel.addMouseListener(moveNotepadListener);
		rightPanel.addMouseMotionListener(menuCtrlListener);
		rightPanel.addMouseMotionListener(moveNotepadListener);
		rightPanel.addContainerListener(containerListener);
	}

	public void menuAct(int state) {
		setMenuState(state);

		synchronized (inActionLock) {
			if (!inAction) {
				inAction = true;

				executorActor();
			}
		}
	}

	private void executorActor() {
		executor.execute(new Runnable() {

			@Override
			public void run() {
				final int initValue;
				if (lastValidMenuState == MENU_STATE_SHOWING) {
					initValue = maxMenuBorderThickness;
				} else {
					initValue = minMenuBorderThickness;
				}

				int thickness = initValue, tempVector = vector;
				while (true) {
					getBorderPanel().setMenuBorderThickness(thickness, thickness, thickness, thickness);

					log.trace("setMenuBorderThickness({}, {}, {}, {})", new Object[] { thickness, thickness, thickness,
							thickness });

					try {
						TimeUnit.MILLISECONDS.sleep(timeInterval);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}

					synchronized (menuStateLock) {
						if (getMenuState() == MENU_STATE_SHOWING) {
							if (thickness >= maxMenuBorderThickness) {
								lastValidMenuState = MENU_STATE_SHOWING;
								break;
							}

							if (thickness != maxMenuBorderThickness && thickness + vector > maxMenuBorderThickness) {
								tempVector = maxMenuBorderThickness - thickness;
							} else {
								tempVector = vector;
							}
						} else {
							if (thickness <= minMenuBorderThickness) {
								lastValidMenuState = MENU_STATE_HIDING;
								break;
							}

							if (thickness != minMenuBorderThickness && thickness + vector < minMenuBorderThickness) {
								tempVector = minMenuBorderThickness - thickness;
							} else {
								tempVector = vector;
							}
						}

						thickness += tempVector;
					}
				}

				synchronized (inActionLock) {
					inAction = false;
				}
			}

		});
	}

	private void calculateVector() {
		vector = rise * menuState;
	}

	/**
	 * @return the notepad
	 */
	public Notepad getNotepad() {
		return notepad;
	}

	/**
	 * @param notepad
	 *            the notepad to set
	 */
	public void setNotepad(Notepad notepad) {
		this.notepad = notepad;
	}

	/**
	 * @return the menuPanel
	 */
	public NotepadBorderPanel getBorderPanel() {
		return borderPanel;
	}

	/**
	 * @param menuPanel
	 *            the menuPanel to set
	 */
	public void setBorderPanel(NotepadBorderPanel borderPanel) {
		this.borderPanel = borderPanel;
		this.borderPanel.setMaxMenuBorderThickness(maxMenuBorderThickness);
	}

	/**
	 * @return the minMenuBorderThickness
	 */
	public int getMinMenuBorderThickness() {
		return minMenuBorderThickness;
	}

	/**
	 * @param minMenuBorderThickness
	 *            the minMenuBorderThickness to set
	 */
	public void setMinMenuBorderThickness(int minMenuBorderThickness) {
		this.minMenuBorderThickness = minMenuBorderThickness;
	}

	/**
	 * @return the maxMenuBorderThickness
	 */
	public int getMaxMenuBorderThickness() {
		return maxMenuBorderThickness;
	}

	/**
	 * @param maxMenuBorderThickness
	 *            the maxMenuBorderThickness to set
	 */
	public void setMaxMenuBorderThickness(int maxMenuBorderThickness) {
		this.maxMenuBorderThickness = maxMenuBorderThickness;
	}

	/**
	 * @return the rise
	 */
	public int getRise() {
		return rise;
	}

	/**
	 * @param rise
	 *            the rise to set
	 */
	public void setRise(int rise) {
		if (this.rise != rise) {
			this.rise = rise;
			calculateVector();
		}
	}

	/**
	 * @return the menuState
	 */
	public int getMenuState() {
		synchronized (menuStateLock) {
			return menuState;
		}
	}

	/**
	 * @param menuState
	 *            the menuState to set
	 */
	protected void setMenuState(int menuState) {
		synchronized (menuStateLock) {
			if (this.menuState != menuState) {
				this.menuState = menuState;
				calculateVector();
			}
		}
	}

	/**
	 * @return the timeInterval
	 */
	public long getTimeInterval() {
		return timeInterval;
	}

	/**
	 * @param timeInterval
	 *            the timeInterval to set
	 */
	public void setTimeInterval(long timeInterval) {
		this.timeInterval = timeInterval;
	}
}
